#if !defined(PROFILE2_H)
#define PROFILE2_H 

#include <string>
#include <map>
#include <set>
#include <sstream>
#include <stdexcept>

/**
 * Parses contents like
 *		#inlcude "filename"
 *		name1 = value1 // comment
 *		name2 = value2
 * or
 *		"name1 = value1; name2 = value2"
 * then returns value of requested type by given name.
 */
class profile2 {
public:
	typedef std::string string;

	class error : public std::runtime_error {
	public:
		error(const string & s) : std::runtime_error(s) { }
	};
	/**
	 * Thrown when profile is unable to read from the source stream
	 */
	class bad_source : public error {
	public:
		bad_source(const string & src)
			: error(string("Bad profile source") + (src.empty() ? "" : ": ") + src) { }
	};
	/**
	 * Thrown when the name of the requested parameter is not found
	 */
	class not_found : public error {
	public:
		not_found(const string & name)
			: error(string("<") + name + "> not found") { }
	};
	/**
	 * Thrown when parameter value does not match requested type
	 */
	class parse_error : public error {
	public:
		parse_error(const string & name, const string & value)
			: error(string("Cannot parse value of <") + name + ">: <" + value + ">") { }
	};
	/**
	 * Thrown when include statement is not valid
	 */
	class bad_include : public error {
	public:
		bad_include(const string & s)
			: error(string("Bad include statement <") + s + ">") { }
	};
public:
	profile2();
	/// Creates profile from a stream; 
	/// if nothrow, bad_source and bad_include are never thrown
	profile2(std::istream & in, bool nothrow = false);
	/// Creates profile from a file; 
	/// if nothrow, bad_source and bad_include are never thrown
	profile2(const string & file, bool nothrow = false);
	~profile2();
public:
	/// Reads profile from a stream; 
	/// if nothrow, bad_source and bad_include are never thrown
	void append(std::istream & in, bool nothrow = false);
	/// Reads profile from a file; 
	/// if nothrow, bad_source and bad_include are never thrown
	void appendf(const string & name, bool nothrow = false);
	/// Reads profile from a string; 
	/// if nothrow, bad_source and bad_include are never thrown
	void appends(const string & s, bool nothrow = false);
	/// Appends another profile (existing values are overriden)
	void append(const profile2 &);
	void clear();
	void swap(profile2 & partner);
public:
	std::ios & parser() const
		{ return _parser; }
public:
	/// If error occurs, an exception is thrown
	template <typename T> 
	T get(string name) const;
	/// If error occurs, default value is returned
	template <typename T> 
	T get(string name, const T & def) const;
	/// Returns true if given parameter exists
	bool query(string name) const;
	/// Returns true if given parameter exists; 
	/// if parse error occurs, an exception is thrown
	template <typename T> 
	bool query(string name, T & value) const;
	/// Returns true if given parameter exists; 
	/// if parse error occurs, default value is returned
	template <typename T> 
	bool query(string name, T & value, const T & def) const;
	/// Returns true if there are unused parameters; 
	/// if pname != 0, receives the name of the first unused parameter found
	bool unused(string * pname = 0) const;
public:
	enum output_state { output_all, output_used, output_unused };
	output_state output() const
		{ return _output; }
	void output(output_state o)
		{ _output = o; }
	friend std::ostream & operator << (std::ostream & s, const profile2 & p);
private:
	// Contains additional usage tag
	struct value {
		mutable bool	used;
		string			str;
	public:
		value()	
			: used(false) { }
		value(const string & s, bool u = false)
			: used(u), str(s) { }
	};	
	typedef std::map<string, value> map_type;
	mutable map_type			_items;
	mutable std::istringstream	_parser;
	output_state				_output;
private:
	// Trims all white characters from both ends
	void trim(string & s) const;
	// Appends contents of the stream to the map
	void append(std::istream & in, std::set<string> & includes, const string & src, bool nothrow);
	void parse(const string & s, std::set<string> & includes, bool nothrow);
};
	   
#include "profile2.inl"

#endif // PROFILE2_H
